﻿/*
Bf3Rcon.NET, provides a .NET implementation of BF3's RCON interface.
Copyright (C) 2011 Timi

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

You can contact us at http://bf3rcon.codeplex.com/.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System.Net.Battlefield3
{
    /// <summary>
    /// Represents an entry in the server's banlist.
    /// </summary>
    [Serializable()]
    public class Ban : IEquatable<Ban>
    {
        /// <summary>
        /// Gets what kind of information will be banned.
        /// </summary>
        /// <value>The type of information that will be found in <see cref="Target"/>.</value>
        public BanTargetType TargetType { get; private set; }

        /// <summary>
        /// Gets the target of the <see cref="Ban"/>.
        /// </summary>
        /// <value>The target of the <see cref="Ban"/>.</value>
        public string Target { get; private set; }

        /// <summary>
        /// Gets the duration information of the <see cref="Ban"/>.
        /// </summary>
        /// <value>The duration information of the <see cref="Ban"/>.</value>
        public BanDurationType DurationType { get; private set; }

        /// <summary>
        /// Gets the duration of the ban, in units determined by <see cref="DurationType"/>.
        /// </summary>
        /// <value>The duration of the ban, in units determined by <see cref="DurationType"/>.</value>
        public int Duration { get; private set; }

        readonly DateTime ExpireTime; //will be used only if durationtype is seconds

        /// <summary>
        /// Gets the approximate amount of time left in the <see cref="Ban"/>, if <see cref="DurationType"/> is <see cref="BanDurationType.Seconds"/>.
        /// </summary>
        /// <value>The approximate amount of time left in the <see cref="Ban"/> or null if <see cref="DurationType"/>
        /// isn't <see cref="BanDurationType.Seconds"/>.</value>
        public TimeSpan? TimeLeft
        {
            get
            {
                if (DurationType == BanDurationType.Seconds)
                    return ExpireTime - DateTime.Now;
                return null;
            }
        }

        /// <summary>
        /// Gets the approximate time the <see cref="Ban"/> will expire, if <see cref="DurationType"/> is <see cref="BanDurationType.Seconds"/>.
        /// </summary>
        /// <value>The approximate time the <see cref="Ban"/> will expire or null if <see cref="DurationType"/>
        /// isn't <see cref="BanDurationType.Seconds"/>.</value>
        public DateTime? TimeExpiration
        {
            get
            {
                if (DurationType == BanDurationType.Seconds)
                    return ExpireTime;
                return null;
            }
        }

        /// <summary>
        /// Gets the reason of the <see cref="Ban"/>.
        /// </summary>
        /// <value>The reason of the <see cref="Ban"/>.</value>
        public string Reason { get; private set; }
        
        /// <summary>
        /// Creates a new instance of <see cref="Ban"/> that will contain all of the information required to ban a <see cref="Player"/>,
        /// not including a reason.
        /// </summary>
        /// <param name="targetType">The information that will be used to implement the ban.</param>
        /// <param name="target">The target of the ban.</param>
        /// <param name="durationType">How long the ban will last.</param>
        /// <param name="duration">If <paramref name="durationType"/> is <see cref="BanDurationType.Rounds"/>,
        /// the number of rounds. If <see cref="BanDurationType.Seconds"/>, the number of seconds.
        /// If <see cref="BanDurationType.Permanent"/>, then this value won't matter.
        /// </param>
        /// <remarks>
        /// //TODO: add usage examples when fully implemented
        /// </remarks>
        public Ban(BanTargetType targetType, string target, BanDurationType durationType, int duration)
            : this(targetType, target, durationType, duration, "")
        {}

        /// <summary>
        /// Creates a new instance of <see cref="Ban"/> that will contain all of the information required to ban a <see cref="Player"/>.
        /// </summary>
        /// <param name="targetType">The information that will be used to implement the ban.</param>
        /// <param name="target">The target of the ban.</param>
        /// <param name="durationType">How long the ban will last.</param>
        /// <param name="duration">If <paramref name="durationType"/> is <see cref="BanDurationType.Rounds"/>,
        /// the number of rounds. If <see cref="BanDurationType.Seconds"/>, the number of seconds.
        /// If <see cref="BanDurationType.Permanent"/>, then this value won't matter.
        /// </param>
        /// <param name="reason">The reason the ban is being issued.</param>
        /// <remarks>
        /// //TODO: add usage examples when fully implemented
        /// </remarks>
        public Ban(BanTargetType targetType, string target, BanDurationType durationType, int duration, string reason)
        {
            TargetType = targetType;
            Target = target;
            DurationType = durationType;
            Duration = duration;
            Reason = reason;

            if (DurationType == BanDurationType.Seconds)
                ExpireTime = DateTime.Now + TimeSpan.FromSeconds(duration);
        }

        #region Factory methods
        /// <summary>
        /// Creates a permanent <see cref="Ban"/> for a <see cref="Player"/>, which will ban the <see cref="Player.Guid"/>.
        /// </summary>
        /// <param name="player">The <see cref="Player"/> that will be banned.</param>
        /// <returns>A <see cref="Ban"/> object that will ban the <see cref="Player"/> permanently when finally issued.</returns>
        public static Ban PermanentBan(Player player)
        {
            return new Ban(BanTargetType.Guid, player.Guid, BanDurationType.Permanent, 0);
        }

        /// <summary>
        /// Creates a time <see cref="Ban"/> (in seconds) for a <see cref="Player"/>, which will ban the <see cref="Player.Guid"/>.
        /// </summary>
        /// <param name="player">The <see cref="Player"/> that will be banned.</param>
        /// <param name="seconds">The number of seconds the <see cref="Player"/> will be banned.</param>
        /// <returns>A <see cref="Ban"/> object that will ban the <see cref="Player"/> for a number of seconds when finally issued.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="seconds"/> must be greater than zero.</exception>
        public static Ban TimeBan(Player player, int seconds)
        {
            if (seconds < 1) throw new ArgumentOutOfRangeException("seconds", "seconds must be greater than zero.");

            return new Ban(BanTargetType.Guid, player.Guid, BanDurationType.Seconds, seconds);
        }

        /// <summary>
        /// Creates a round <see cref="Ban"/> for a <see cref="Player"/>, which will ban the <see cref="Player.Guid"/>.
        /// </summary>
        /// <param name="player">The <see cref="Player"/> that will be banned.</param>
        /// <param name="rounds">The number of rounds the <see cref="Player"/> will be banned.</param>
        /// <returns>A <see cref="Ban"/> object that will ban the <see cref="Player"/> for a number of rounds when finally issued.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="rounds"/> must be greater than zero.</exception>
        public static Ban RoundBan(Player player, int rounds)
        {
            if (rounds < 1) throw new ArgumentOutOfRangeException("rounds", "rounds must be greater than zero.");

            return new Ban(BanTargetType.Guid, player.Guid, BanDurationType.Rounds, rounds);
        }

        //These add reasons. was simple copy and paste

        /// <summary>
        /// Creates a permanent <see cref="Ban"/> for a <see cref="Player"/>, which will ban the <see cref="Player.Guid"/>.
        /// </summary>
        /// <param name="player">The <see cref="Player"/> that will be banned.</param>
        /// <param name="reason">The reason the <see cref="Player"/> is being banned.</param>
        /// <returns>A <see cref="Ban"/> object that will ban the <see cref="Player"/> permanently when finally issued.</returns>
        public static Ban PermanentBan(Player player, string reason)
        {
            return new Ban(BanTargetType.Guid, player.Guid, BanDurationType.Permanent, 0, reason);
        }

        /// <summary>
        /// Creates a time <see cref="Ban"/> (in seconds) for a <see cref="Player"/>, which will ban the <see cref="Player.Guid"/>.
        /// </summary>
        /// <param name="player">The <see cref="Player"/> that will be banned.</param>
        /// <param name="seconds">The number of seconds the <see cref="Player"/> will be banned.</param>
        /// <param name="reason">The reason the <see cref="Player"/> is being banned.</param>
        /// <returns>A <see cref="Ban"/> object that will ban the <see cref="Player"/> for a number of seconds when finally issued.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="seconds"/> must be greater than zero.</exception>
        public static Ban TimeBan(Player player, int seconds, string reason)
        {
            if (seconds < 1) throw new ArgumentOutOfRangeException("seconds", "seconds must be greater than zero.");

            return new Ban(BanTargetType.Guid, player.Guid, BanDurationType.Seconds, seconds, reason);
        }

        /// <summary>
        /// Creates a round <see cref="Ban"/> for a <see cref="Player"/>, which will ban the <see cref="Player.Guid"/>.
        /// </summary>
        /// <param name="player">The <see cref="Player"/> that will be banned.</param>
        /// <param name="rounds">The number of rounds the <see cref="Player"/> will be banned.</param>
        /// <param name="reason">The reason the <see cref="Player"/> is being banned.</param>
        /// <returns>A <see cref="Ban"/> object that will ban the <see cref="Player"/> for a number of rounds when finally issued.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="rounds"/> must be greater than zero.</exception>
        public static Ban RoundBan(Player player, int rounds, string reason)
        {
            if (rounds < 1) throw new ArgumentOutOfRangeException("rounds", "rounds must be greater than zero.");

            return new Ban(BanTargetType.Guid, player.Guid, BanDurationType.Rounds, rounds, reason);
        }

        //These now allow a customizable bantargetype, another simple copy and paste

        /// <summary>
        /// Creates a permanent <see cref="Ban"/> for a <see cref="Player"/>, which will ban the <see cref="Player.Guid"/>.
        /// </summary>
        /// <param name="player">The <see cref="Player"/> that will be banned.</param>
        /// <param name="targetType">The <see cref="BanTargetType"/> that indicates what about the <see cref="Player"/> will be banned.</param>
        /// <returns>A <see cref="Ban"/> object that will ban the <see cref="Player"/> permanently when finally issued.</returns>
        public static Ban PermanentBan(Player player, BanTargetType targetType)
        {
            return new Ban(targetType, player.Guid, BanDurationType.Permanent, 0);
        }

        /// <summary>
        /// Creates a time <see cref="Ban"/> (in seconds) for a <see cref="Player"/>, which will ban the <see cref="Player.Guid"/>.
        /// </summary>
        /// <param name="player">The <see cref="Player"/> that will be banned.</param>
        /// <param name="targetType">The <see cref="BanTargetType"/> that indicates what about the <see cref="Player"/> will be banned.</param>
        /// <param name="seconds">The number of seconds the <see cref="Player"/> will be banned.</param>
        /// <returns>A <see cref="Ban"/> object that will ban the <see cref="Player"/> for a number of seconds when finally issued.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="seconds"/> must be greater than zero.</exception>
        public static Ban TimeBan(Player player, BanTargetType targetType, int seconds)
        {
            if (seconds < 1) throw new ArgumentOutOfRangeException("seconds", "seconds must be greater than zero.");

            return new Ban(targetType, player.Guid, BanDurationType.Seconds, seconds);
        }

        /// <summary>
        /// Creates a round <see cref="Ban"/> for a <see cref="Player"/>, which will ban the <see cref="Player.Guid"/>.
        /// </summary>
        /// <param name="player">The <see cref="Player"/> that will be banned.</param>
        /// <param name="targetType">The <see cref="BanTargetType"/> that indicates what about the <see cref="Player"/> will be banned.</param>
        /// <param name="rounds">The number of rounds the <see cref="Player"/> will be banned.</param>
        /// <returns>A <see cref="Ban"/> object that will ban the <see cref="Player"/> for a number of rounds when finally issued.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="rounds"/> must be greater than zero.</exception>
        public static Ban RoundBan(Player player, BanTargetType targetType, int rounds)
        {
            if (rounds < 1) throw new ArgumentOutOfRangeException("rounds", "rounds must be greater than zero.");

            return new Ban(targetType, player.Guid, BanDurationType.Rounds, rounds);
        }

        /// <summary>
        /// Creates a permanent <see cref="Ban"/> for a <see cref="Player"/>, which will ban the <see cref="Player.Guid"/>.
        /// </summary>
        /// <param name="player">The <see cref="Player"/> that will be banned.</param>
        /// <param name="targetType">The <see cref="BanTargetType"/> that indicates what about the <see cref="Player"/> will be banned.</param>
        /// <param name="reason">The reason the <see cref="Player"/> is being banned.</param>
        /// <returns>A <see cref="Ban"/> object that will ban the <see cref="Player"/> permanently when finally issued.</returns>
        public static Ban PermanentBan(Player player, BanTargetType targetType, string reason)
        {
            return new Ban(targetType, player.Guid, BanDurationType.Permanent, 0, reason);
        }

        /// <summary>
        /// Creates a time <see cref="Ban"/> (in seconds) for a <see cref="Player"/>, which will ban the <see cref="Player.Guid"/>.
        /// </summary>
        /// <param name="player">The <see cref="Player"/> that will be banned.</param>
        /// <param name="targetType">The <see cref="BanTargetType"/> that indicates what about the <see cref="Player"/> will be banned.</param>
        /// <param name="seconds">The number of seconds the <see cref="Player"/> will be banned.</param>
        /// <param name="reason">The reason the <see cref="Player"/> is being banned.</param>
        /// <returns>A <see cref="Ban"/> object that will ban the <see cref="Player"/> for a number of seconds when finally issued.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="seconds"/> must be greater than zero.</exception>
        public static Ban TimeBan(Player player, BanTargetType targetType, int seconds, string reason)
        {
            if (seconds < 1) throw new ArgumentOutOfRangeException("seconds", "seconds must be greater than zero.");

            return new Ban(targetType, player.Guid, BanDurationType.Seconds, seconds, reason);
        }

        /// <summary>
        /// Creates a round <see cref="Ban"/> for a <see cref="Player"/>, which will ban the <see cref="Player.Guid"/>.
        /// </summary>
        /// <param name="player">The <see cref="Player"/> that will be banned.</param>
        /// <param name="targetType">The <see cref="BanTargetType"/> that indicates what about the <see cref="Player"/> will be banned.</param>
        /// <param name="rounds">The number of rounds the <see cref="Player"/> will be banned.</param>
        /// <param name="reason">The reason the <see cref="Player"/> is being banned.</param>
        /// <returns>A <see cref="Ban"/> object that will ban the <see cref="Player"/> for a number of rounds when finally issued.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="rounds"/> must be greater than zero.</exception>
        public static Ban RoundBan(Player player, BanTargetType targetType, int rounds, string reason)
        {
            if (rounds < 1) throw new ArgumentOutOfRangeException("rounds", "rounds must be greater than zero.");

            return new Ban(targetType, player.Guid, BanDurationType.Rounds, rounds, reason);
        }
        #endregion

        /// <summary>
        /// Specifies whether this <see cref="Ban"/> is the same as the specified object.
        /// </summary>
        /// <param name="obj">The object to test.</param>
        /// <returns>True if this <see cref="Ban"/> is the same as the object.</returns>
        public override bool Equals(object obj)
        {
            if (obj == null) return base.Equals(obj);

            Ban b = obj as Ban;
            if (b == null) return false;
            return Equals(b);
        }

        /// <summary>
        /// Returns the hash code of this <see cref="Ban"/>.
        /// </summary>
        /// <returns>An integer value specifying the hash value for this <see cref="Ban"/>.</returns>
        public override int GetHashCode()
        {
            return (TargetType.ToString() + Target).GetHashCode();
        }

        /// <summary>
        /// Converts the value of this instance to it's equivalent string representation.
        /// </summary>
        /// <returns>The string representation of the <see cref="Ban"/>.</returns>
        public override string ToString()
        {
            if (DurationType == BanDurationType.Permanent)
                return "{0}={1} Permanent".Format2(TargetType, Target);

            return "{0}={1} {2}={3}".Format2(TargetType, Target, DurationType, Duration);
        }

        /// <summary>
        /// Specifies whether this <see cref="Ban"/> is the same as the specified <see cref="Ban"/>.
        /// </summary>
        /// <param name="other">The <see cref="Ban"/> to test.</param>
        /// <returns>True if this <see cref="Ban"/> is the same as the other <see cref="Ban"/> by 
        /// <see cref="TargetType"/> and <see cref="Target"/>.</returns>
        public bool Equals(Ban other)
        {
            if (other == null) return false;
            return other.TargetType == TargetType && other.Target == Target;
        }
    }
}
